/*
 *  manager.cpp
 *  xbox360wireless_receiver
 *
 *  Created by Delio Brignoli on 12/12/08.
 *  Copyright 2008 Delio Brignoli. All rights reserved.
 *
 */

#include <IOKit/IOLib.h>
#include <IOKit/usb/IOUSBInterface.h>
#include "manager.h"
#include "commands.h"
#include "debug.h"

extern "C" {
#include <pexpert/pexpert.h>//This is for debugging purposes ONLY
}

// Define my superclass
#define super IOService

OSDefineMetaClassAndStructors(WGRManager, IOService)

static UInt32 maxPacketSizeForPipe(IOUSBPipe *pipe)
{
    const IOUSBEndpointDescriptor *endpointDescriptor = pipe->GetEndpointDescriptor();
    if(!endpointDescriptor)
		return 0;
	return endpointDescriptor->wMaxPacketSize;
}

bool WGRManager::start(IOService *provider)
{	
	bool result = false;

    if (!super::start(provider)) {
        goto Done;
    }
	
	//Initialize class variables
	mngrIndex = -1;
	outstandingIOCount = 0;
	usbInterface - NULL;
	registerHeadsetDriver(NULL);
	headsetState = HEADSET_DISCONNECTED;
	registerControllerDriver(NULL);
	controllerState = CONTROLLER_DISCONNECTED;
	inPipe = NULL;
	outPipe = NULL;
	nanoseconds_to_absolutetime(0, (uint64_t *)&lastConnectionUpdateTimestamp);
	serviceName = NULL;
    
	usbInterface = OSDynamicCast(IOUSBInterface, provider);
    if (!usbInterface) {
		dIOLog(ERROR, "Provider object is not an interface. Exiting.\n");
        goto Done;
    }

	if (!usbInterface->open(this)) {
		dIOLog(ERROR, "Could not open the USB interface. Exiting.\n");
        goto Done;	
	}
	
	mngrIndex = ((int)usbInterface->GetInterfaceNumber())/2;
	
	IOUSBFindEndpointRequest endpointRequest;
	
	//Find interrupt in pipe
	endpointRequest.type =	kUSBInterrupt;
	endpointRequest.direction = kUSBIn;
	endpointRequest.maxPacketSize = 0;
	endpointRequest.interval = 0;
	inPipe = usbInterface->FindNextPipe(NULL, &endpointRequest);
	if (!inPipe) {
		dIOLog(ERROR, "Could not find the interrupt in pipe. Exiting.\n");
		goto Done;
	} else {
		inPipe->retain();
	}

	//Find interrupt out pipe
	endpointRequest.type =	kUSBInterrupt;
	endpointRequest.direction = kUSBOut;
	endpointRequest.maxPacketSize = 0;
	endpointRequest.interval = 0;
	outPipe = usbInterface->FindNextPipe(NULL, &endpointRequest);
	if (!outPipe) {
		dIOLog(ERROR, "Could not find the interrupt out pipe. Exiting.\n");
		goto Done;
	} else {
		outPipe->retain();
	}

	//make sure the mngrIndex string representation is one char long
	assert(mngrIndex >= 0 && mngrIndex <= 9);
	serviceName = OSString::withCString(stringify(WGRManager) "_X");
	if (!serviceName) {
		dIOLog(ERROR, "Resource name string allocation failed. Exiting.\n");
		goto Done;
	}
	serviceName->setChar('1' + (char)mngrIndex, serviceName->getLength()-1);
	
	setName(serviceName->getCStringNoCopy());
	registerService();
		
	//Init CMD. Do not really know what it does but the original driver does it so we do it too...
	disableContinuousReports();
	
	//Update connection status on startup so the headset driver can be loaded if necessary
	requestConnectionUpdateReport();
	
	//Kick start reading from the interrupt pipe
	if (!requestAsyncRead()) {
		dIOLog(ERROR, "Could not kick start reading from the input interrupt pipe. Exiting.\n");
		goto Done;
	}
	
	result = true;

Done:
	
	if (!result) {
		cleanUp(provider);
	}
	
	return result;
}

bool WGRManager::willTerminate(IOService * provider, IOOptionBits options) {
	if (headsetDriver) {
		terminateHeadsetDriver();
	}
	
	if (inPipe) {
		inPipe->Abort();
	}

	//We could probably let the outstanding writes run to completion, but we abort them just to be sure..
	if (outPipe) {
		outPipe->Abort();
	}

	return super::willTerminate(provider, options);
}

bool WGRManager::didTerminate( IOService * provider, IOOptionBits options, bool * defer ) {
	if (outstandingIOCount == 0 && usbInterface) {
		usbInterface->close(this);
		usbInterface = NULL;
    }
	
    return super::didTerminate(provider, options, defer);
}

void WGRManager::cleanUp(IOService *provider) {

	if (serviceName) {
		serviceName->release();
		serviceName = NULL;
	}

	if (inPipe) {
		inPipe->release();
		inPipe = NULL;
	}

	if (outPipe) {
		outPipe->release();
		outPipe = NULL;
	}

	if (headsetDriver) {
		headsetDriver->release();
		headsetDriver = NULL;
	}

	if (controllerDriver) {
		controllerDriver->release();
		controllerDriver = NULL;
	}
	
    if (usbInterface) {
		usbInterface->close(this);
		usbInterface = NULL;
    }

	assert(outstandingIOCount == 0);
	mngrIndex = -1;
}

IOReturn WGRManager::message(UInt32 type,IOService *provider,void *argument) {
	dIOLog(INFO, "Message: %d, %x, %x.\n", type, provider, argument);
	return IOService::message(type,provider,argument);
}

bool WGRManager::requestAsyncRead(void) {
	if (isInactive()) {
		dIOLog(INFO, "We are being terminated, do not allow any more transfers.\n");
		return false;
	}
	
	IOBufferMemoryDescriptor *inBuf = IOBufferMemoryDescriptor::inTaskWithOptions(kernel_task, 0, maxPacketSizeForPipe(inPipe));
	if (!inBuf) {
		dIOLog(ERROR, "Could not allocate buffer for the interrupt in pipe.\n");
		return false;
	}
	
    readCompletion.target = this;
    readCompletion.action = _readComplete;
    readCompletion.parameter = inBuf;
    
    IOReturn err = inPipe->Read(inBuf, READ_TIMEOUT, READ_TIMEOUT, inBuf->getLength(), &readCompletion);
    if (err != kIOReturnSuccess) {
		dIOLog(ERROR, "Read did not start (%x,%d,%d,%d,%x), error: %s\n",inBuf, READ_TIMEOUT, 
																		READ_TIMEOUT, inBuf->getLength(), 
																		&readCompletion, 
																		(stringFromReturn(err)?stringFromReturn(err):"unknown"));
		inBuf->release();
        return false;
	}
	
	outstandingIOCount++;
	return true;
}

bool WGRManager::asyncWrite(UInt8 *buf, int bufLength) {
	if (isInactive()) {
		dIOLog(INFO, "We are being terminated, do not allow any more transfers.\n");
		return false;
	}
	
	IOBufferMemoryDescriptor *outBuf = IOBufferMemoryDescriptor::inTaskWithOptions(kernel_task, 0, maxPacketSizeForPipe(outPipe));
	if (!outBuf) {
		dIOLog(ERROR, "Could not allocate buffer for the interrupt out pipe.\n");
		return false;
	}

	outBuf->writeBytes(0, buf, bufLength);

    writeCompletion.target = this;
    writeCompletion.action = _writeComplete;
    writeCompletion.parameter = outBuf;
	
    IOReturn err = outPipe->Write(outBuf, WRITE_TIMEOUT, WRITE_TIMEOUT, bufLength, &writeCompletion);
    if (err != kIOReturnSuccess) {
		dIOLog(ERROR, "Write did not start (%x,%d,%d,%d,%x), error: %s\n",outBuf, WRITE_TIMEOUT, 
			   WRITE_TIMEOUT, outBuf->getLength(), 
			   &writeCompletion, 
			   (stringFromReturn(err)?stringFromReturn(err):"unknown"));
		outBuf->release();
        return false;
	}

	outstandingIOCount++;
	return true;
}

// Static wrapper for read notifications
void WGRManager::_readComplete(void *target, void *parameter, IOReturn status, UInt32 bufferSizeRemaining, AbsoluteTime timeStamp) {
    if (target)
        ((WGRManager *)target)->readComplete(parameter, status, bufferSizeRemaining, timeStamp);
}

// Handle a completed read on a controller
void WGRManager::readComplete(void *parameter, IOReturn status, UInt32 bufferSizeRemaining, AbsoluteTime timeStamp) {    

	IOBufferMemoryDescriptor *inBuf = (IOBufferMemoryDescriptor *)parameter;	
    char s[1024];
	char *data = (char*)inBuf->getBytesNoCopy();
    int i, length;
	length = inBuf->getLength() - bufferSizeRemaining;
    
    for (i = 0; i < length; i++)
    {
        s[(i * 2) + 0] = "0123456789ABCDEF"[(data[i] & 0xF0) >> 4];
        s[(i * 2) + 1] = "0123456789ABCDEF"[data[i] & 0x0F];
    }
    s[i * 2] = '\0';
    dIOLog(INFO, "Got data (%d bytes): %s\n", length, s);
	
	dIOLog(INFO, "Read completed with status: %s\n",(stringFromReturn(status)?stringFromReturn(status):"unknown"));
    
	switch (status)
    {
        case kIOReturnOverrun:
			//Lost data, clear stall
			inPipe->ClearPipeStall(true);
        case kIOReturnSuccess:
			handleInputReport((UInt8*)inBuf->getBytesNoCopy(), inBuf->getLength() - bufferSizeRemaining);
			//Request an update report on the controller/haedset connection status if it is due
			if (timeStamp.hi > lastConnectionUpdateTimestamp.hi) {
				dIOLog(INFO, "Connection status polling interval elapsed, requesting update.\n");
				lastConnectionUpdateTimestamp = timeStamp;
				requestConnectionUpdateReport();
			}			
        case kIOReturnTimeout:
			//Transfer timed out, try again
			requestAsyncRead();
			break;
        default:
            break;
    }

	if (inBuf) {
		inBuf->release();
	}

	//Close our provider if we are the last transfer and being terminated
	if (--outstandingIOCount == 0 && usbInterface && isInactive()) {
		usbInterface->close(this);
		usbInterface = NULL;
    }
}

// Static wrapper for write notifications
void WGRManager::_writeComplete(void *target, void *parameter, IOReturn status, UInt32 bufferSizeRemaining)
{
    if (target)
        ((WGRManager *)target)->writeComplete(parameter, status, bufferSizeRemaining);
}

// Handle a completed write on a controller
void WGRManager::writeComplete(void *parameter,IOReturn status,UInt32 bufferSizeRemaining)
{
	IOBufferMemoryDescriptor *outBuf = (IOBufferMemoryDescriptor *)parameter;
	if (outBuf) {
		outBuf->release();
	}
	
	switch(status) {
		case kIOUSBPipeStalled:
			dIOLog(INFO, "Write pipe stalled, resetting.\n");
			outPipe->ClearPipeStall(true);
			break;
		case kIOReturnSuccess:
			break;
		default:
			break;
	}

	dIOLog(INFO, "Write completed with status: %s\n",(stringFromReturn(status)?stringFromReturn(status):"unknown"));

	//Close our provider if we are the last transfer and being terminated
	if (--outstandingIOCount == 0 && usbInterface && isInactive()) {
		usbInterface->close(this);
		usbInterface = NULL;
    }	
}

/*
 The core of the manager is stateless. If we receive continuous updates, we turn them off.
 If we receive an input report we process it. If we receive a connection/disconnection
 update we register/terminate the coresponding driver.
 */
void WGRManager::handleInputReport(UInt8 *reportBuffer, int bufferLenght) {

	//Classify the input report. The following IF statements are ordered for
	//decresing frequency.

	// Report Update
	if (reportBuffer[0] == 0x01 && reportBuffer[1] == 0x01) {
		return;
	}

	// Connection Update
	if (reportBuffer[0] == 0x08 && bufferLenght == 2) {
		//Process Headset flags in the update
		if (reportBuffer[1] & 0x40)
			handleHeadsetConnection();
		else
			handleHeadsetDisconnection();
		//Process Controller flags in the update
		if (reportBuffer[1] & 0x80)
			handleControllerConnection();
		else
			handleControllerDisconnection();
		return;
	}

	// Continuous report update
	if (reportBuffer[0] == 0x01 && reportBuffer[1] == 0xFF) {
		dIOLog(INFO, "Received a continuous input report, disabling continuous reports.\n");
		disableContinuousReports();
		return;
	}

	//TODO: Format buffer as hex string and append to the following log statement.
	dIOLog(ERROR, "Unknown input report\n");
}

void WGRManager::stop(IOService *provider)
{	
	cleanUp(provider);
    super::stop(provider);
}

bool WGRManager::isHeadsetReady()
{
    return ( headsetState > HEADSET_DISCONNECTED );
}

bool WGRManager::isControllerReady()
{
    return ( controllerState > CONTROLLER_DISCONNECTED );
}

void WGRManager::registerHeadsetDriver(IOService *driver) {
	if (isInactive())
		return;

	dIOLog(INFO, "Registering headset with address %x (old headset driver address %x)\n", driver, headsetDriver);
	//If nothing is changing, return
	if (headsetDriver == driver)
		return;
	//If something was registered release it
	if (headsetDriver)
		headsetDriver->release();
	//Assign new driver address
	headsetDriver = driver;
	//If the new address is != NULL, retain it
	if (headsetDriver) {
		headsetDriver->retain();
		successBeep();
	}
}

void WGRManager::registerControllerDriver(IOService *driver) {
	if (isInactive())
		return;
	
	dIOLog(INFO, "Registering controller with address %x (old controller driver address %x)\n", driver, controllerDriver);
	if (controllerDriver == driver)
		return;
	if (controllerDriver)
		controllerDriver->release();
	controllerDriver = driver;
	if (controllerDriver)
		controllerDriver->retain();
}

void WGRManager::handleHeadsetConnection(void) {
	headsetState = HEADSET_CONNECTED;
	//Pair headset
	if (!pairHeadset()) {
		dIOLog(ERROR, "Headset pairing failed.");
		return;
	}

	headsetState = HEADSET_PAIRED;
	//Re-start IOKit device matching on unused interfaces IFF no headset has regstered
	if (!headsetDriver) {
		triggerDriverMatching();
	}
}

void WGRManager::handleHeadsetDisconnection(void) {
	headsetState = HEADSET_DISCONNECTED;
	//Terminate headset driver
	if (headsetDriver) {
		terminateHeadsetDriver();
	}
}

void WGRManager::handleControllerConnection(void) {
	//TODO: Implement
}

void WGRManager::handleControllerDisconnection(void) {
	//TODO: Implement	
}

void WGRManager::triggerDriverMatching(void) {
	OSIterator *iter = this->getProvider()->getProvider()->getClientIterator();
	if (iter) {
		while (OSObject *tmp = iter->getNextObject()) {
			if (IOUSBInterface *interface = OSDynamicCast(IOUSBInterface, tmp)) {
				if ( interface->GetInterfaceNumber() == (mngrIndex*2+1) && !interface->getClient() ) {
					interface->registerService(kIOServiceSynchronous);
					break;
				}
			}
		}
		iter->release();
	}	
}

void WGRManager::terminateHeadsetDriver(void) {
	OSIterator *iter = this->getProvider()->getProvider()->getClientIterator();
	if (iter) {
		while (OSObject *tmp = iter->getNextObject()) {
			if (IOUSBInterface *interface = OSDynamicCast(IOUSBInterface, tmp)) {
				if ( interface->GetInterfaceNumber() == (mngrIndex*2+1) && interface->getClient() ) {
					interface->getClient()->terminate(kIOServiceSynchronous);
					break;
				}
			}
		}
		iter->release();
	}	
}

void WGRManager::disableContinuousReports(void) {
	asyncWrite( DISABLE_CONTINUOUS_REPORTS_CMD, sizeof(DISABLE_CONTINUOUS_REPORTS_CMD) );
}

bool WGRManager::pairHeadset(void) {
	bool res = asyncWrite( PAIR_HEADSET_CMD_1, sizeof(PAIR_HEADSET_CMD_1) );
	return asyncWrite( PAIR_HEADSET_CMD_2, sizeof(PAIR_HEADSET_CMD_2) ) && res;
}

void WGRManager::successBeep(void) {
	asyncWrite( EMIT_RESET_BEEP_CMD, sizeof(EMIT_RESET_BEEP_CMD) );
	asyncWrite( EMIT_ASCENDING_BEEP_CMD, sizeof(EMIT_ASCENDING_BEEP_CMD) );
}

void WGRManager::failureBeep(void) {
	asyncWrite( EMIT_RESET_BEEP_CMD, sizeof(EMIT_RESET_BEEP_CMD) );
	asyncWrite( EMIT_DESCENDING_BEEP_CMD, sizeof(EMIT_DESCENDING_BEEP_CMD) );
}

void WGRManager::notificationBeep(void) {
	asyncWrite( EMIT_RESET_BEEP_CMD, sizeof(EMIT_RESET_BEEP_CMD) );
	asyncWrite( EMIT_FLAT_BEEP_CMD, sizeof(EMIT_FLAT_BEEP_CMD) );
}

void WGRManager::requestConnectionUpdateReport(void) {
	asyncWrite( REQ_CONNECTION_UPDATE_REPORT_CMD, sizeof(REQ_CONNECTION_UPDATE_REPORT_CMD) );
}
